<?php

/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#namespace tehframework\orm\annotations;

#use \BadMethodCallException;
#use \Reflector;
#use \ReflectionClass;
#use \ezcPersistentObjectDefinition;
#use tehframework\Inflector;
#use tehframework\annotations\DocBlock;
#use tehframework\annotations\DocBlockFactory;
#use tehframework\annotations\Annotation;
#use tehframework\annotations\AnnotationTargetAware;

class EntityAnnotation implements Annotation, AnnotationTargetAware
{
	const ANNOTATION_ID       = 'Id';
	const ANNOTATION_PROPERTY = 'Property';
	const ANNOTATION_RELATION = 'Relation';
	
	/**
	 * @var ReflectionClass
	 */
	protected $target;
	
	/**
	 * @var string?
	 */
	protected $table;
	
	/**
	 * @var PrimaryKeyAnnotation
	 */
	protected $idProperty;
	
	/**
	 * @var PropertyAnnotation[]
	 */
	protected $properties;
	
	/**
	 * @var RelationAnnotation[]
	 */
	protected $relations;
	
	/**
	 * @param  string? $table
	 */
	public function __construct($table = null)
	{
		$this->setTable($table);
	}
	
	/**
	 * @param  string $table
	 * @return EntityAnnotation
	 */
	public function setTable($table)
	{
		if (!empty($table))
		{
			$this->table = $table;
		}
		
		return $this;
	}
	
	/**
	 * @param  DocBlockFactory $docBlockFactory
	 * @return ezcPersistentObjectDefinition
	 */
	public function createDefinition(DocBlockFactory $docBlockFactory)
	{
		$def = new ezcPersistentObjectDefinition(
			$this->createTable(), $this->createClass()
		);
		
		$this->prepareTargetMembers($docBlockFactory);
		
		if ($this->idProperty)
		{
			$this->idProperty->applyDefinition($def);
		}
		
		foreach ($this->properties as $property)
		{
			$property->applyDefinition($def);
		}
		
		$this->idProperty = null;
		$this->properties = array();
		
		return $def;
	}
	
	/**
	 * @param  array<string,ezcPersistentObjectDefinition> $definitionMap
	 * @throws InvalidArgumentException
	 * @throws BadMethodCallException
	 */
	public function applyRelations(array $definitionMap)
	{
		$class = $this->createClass();
		
		if (!isset($definitionMap[$class]))
		{
			throw new InvalidArgumentException(sprintf(
				'Can not apply relations to [%s], ' . 
				'because it is not present in the definition map.',
				$class
			));
		}
		
		foreach ($this->relations as $relation)
		{
			$relation->applyDefinition($definitionMap[$class], $definitionMap);
		}
	}
	
	/**
	 * @return string
	 */
	protected function createTable()
	{
		return empty($this->table) ?
			Inflector::underscore($this->target->name) : $this->table; 
	}
	
	/**
	 * @return string
	 */
	protected function createClass()
	{
		return $this->target->name;
	}
	
	/**
	 * @param  DocBlockFactory $docBlockFactory
	 */
	protected function prepareTargetMembers(DocBlockFactory $docBlockFactory)
	{
		$this->idProperty = null;
		$this->properties = array();
		$this->relations  = array();
		
		foreach ($this->target->getProperties() as $property)
		{
			$this->addTargetMember($docBlockFactory->getDocBlock($property));
		}
		
		foreach ($this->target->getMethods() as $method)
		{
			$this->addTargetMember($docBlockFactory->getDocBlock($method));
		}
	}
	
	/**
	 * @param  DocBlock $docBlock
	 */
	protected function addTargetMember(DocBlock $docBlock)
	{
		if ($docBlock->isAnnotatedWith(self::ANNOTATION_ID))
		{
			$this->idProperty
				= $docBlock->getAnnotation(self::ANNOTATION_ID);
			
			return;
		}
		
		if ($docBlock->isAnnotatedWith(self::ANNOTATION_PROPERTY))
		{
			$this->properties[] = $docBlock->getAnnotation(self::ANNOTATION_PROPERTY);
		}
		
		if ($docBlock->isAnnotatedWith(self::ANNOTATION_RELATION))
		{
			$this->relations[] = $docBlock->getAnnotation(self::ANNOTATION_RELATION);
		}
	}
	
	#region AnnotationTargetAware
	
	/**
	 * @param  Reflector $annotationTarget
	 * @throws BadMethodCallException
	 */
	public function setAnnotationTarget(Reflector $annotationTarget)
	{
		if (!$annotationTarget instanceof ReflectionClass)
		{
			throw new BadMethodCallException();
		}
		
		$this->target = $annotationTarget;
	}
	
	#endregion
	
	#region Annotation
	
	/**
	 * Returns a bitwise flag of targets which can be annotated
	 * with this annotation.
	 * 
	 * @return int
	 *         A bitwise flag of class target. 
	 */
	public function getAnnotationTargets()
	{
		return Annotation::TARGET_CLASS;
	}
	
	/**
	 * Determines whether annotation can be used multiple times
	 * on a single target.
	 * 
	 * @return bool
	 *         Always FALSE. This annotation can not be used multiple times on
	 *         single target.
	 */
	public function allowsMultiple()
	{
		return false;
	}
	
	#endregion
}